package mirrormonkey.util.annotations.parsing;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;

/**
 * A <tt>MemberDataIR</tt> that will match "overridden" fields. A field is
 * considered overridden by this class if a field in a subclass matches the name
 * and the type of the field that this <tt>FieldIR</tt> was created for.<br>
 * 
 * Please note that this does <tt>not</tt> reflect the java language. As fields
 * with same type and name in different classes in the hierarchy are not
 * considered identity-equal and each hold their own respective values, it is
 * not advised to actually write or read values to / from overriding fields
 * (directly or by reflection). Rather, this feature should be used to define
 * additional annotations to the ones from the superclass.<br>
 * 
 * The reflective <tt>Field</tt> <tt>collectedField</tt> will always point to
 * the <tt>Field</tt> that this <tt>FieldIR</tt> was created for.
 * 
 * @author Philipp Christian Loewner
 * 
 */
public class FieldIR extends MemberIR {

	/**
	 * The <tt>Field</tt> that this <tt>FieldIR</tt> was created for or the
	 * first matching <tt>Field</tt> if this <tt>FieldIR</tt> was not created
	 * for a specific <tt>Field</tt> but was created to collect one.
	 */
	protected Field collectedField;

	/**
	 * The type that a <tt>Field</tt> must have to match this <tt>FieldIR</tt>
	 */
	protected Class<?> type;

	/**
	 * The name that a <tt>Field</tt> must have to match this <tt>FieldIR</tt>
	 */
	protected String name;

	/**
	 * This class will only collect fields.
	 */
	public static final Class<?>[] LISTEN_CLASSES = { Field.class };

	/**
	 * Creates a new <tt>FieldIR</tt>. If this constructor is called, this
	 * <tt>FieldIR</tt> is the first <tt>MemberDataIR</tt> for a field.
	 * 
	 * @param parser
	 *            the <tt>AnnotationParser</tt> that parses the class hierarchy
	 *            and creates this <tt>FieldIR</tt>
	 * @param member
	 *            the <tt>Field</tt> that this <tt>FieldIR</tt> is created for
	 */
	public FieldIR(AnnotationParser parser, Field member) {
		super(parser, member);
		parseField(member);
	}

	/**
	 * Creates a new <tt>FieldIR</tt> that will collect the first <tt>Field</tt>
	 * that matches the given <tt>type</tt> and <tt>name</tt>. This constructor
	 * will be called if a certain field is expected due to some other condition
	 * and the field is not yet known.
	 * 
	 * @param parser
	 *            the <tt>AnnotationParser</tt> that parses the class hierarchy
	 *            and creates this <tt>FieldIR</tt>
	 * @param type
	 *            the type of the field that will be collected
	 * @param name
	 *            the name of the field that will be collected
	 */
	public FieldIR(AnnotationParser parser, Class<?> type, String name) {
		super(parser, null);
		this.type = type;
		this.name = name;
	}

	/**
	 * Creates a new <tt>FieldIR</tt> that copies previously collected data from
	 * the previous <tt>FieldIR</tt>. This constructor should be called by
	 * inheriting classes to convert IR classes into one another.
	 * 
	 * @param previous
	 *            the previous <tt>FieldIR</tt> that matched <tt>member</tt>
	 * @param member
	 *            the member for which to create this <tt>FieldIR</tt>
	 */
	public FieldIR(FieldIR previous, Field member) {
		super(previous, member);
		parseField(member);
	}

	/**
	 * Returns the <tt>Field</tt> that this <tt>FieldIR</tt> has collected.
	 * 
	 * @return Either the <tt>Field</tt> that this <tt>FieldIR</tt> has been
	 *         created for or the first encountered <tt>Field</tt> that matched
	 *         this <tt>FieldIR</tt>
	 */
	public Field getCollectedField() {
		return collectedField;
	}

	/**
	 * Sets the collected field, matching type and name if no field has been
	 * collected yet.
	 * 
	 * @param field
	 *            the field that will be collected and used to determine other
	 *            matching fields
	 */
	public void setCollectedField(Field field) {
		if (collectedField == null) {
			collectedField = field;
			type = field.getType();
			name = field.getName();
		}
	}

	@Override
	public void parseConstructor(Constructor<?> constr) {
		throw new RuntimeException(getClass() + ": Cannot parse Constructors.");
	}

	@Override
	public boolean matches(Member member) {
		if (!member.getClass().equals(Field.class)) {
			return false;
		}
		Field compareTo = (Field) member;
		return name.equals(compareTo.getName())
				&& type.equals(compareTo.getType());
	}

	@Override
	public void parseField(Field f) {
		setCollectedField(f);
		parseAnnotatedElement(f);
	}

	@Override
	public void parseMethod(Method m) {
		throw new RuntimeException(getClass() + " can not parse methods.");
	}

	@Override
	public String toString() {
		return "[" + getClass().getName() + ": type: " + type + "; name: "
				+ name + "; collectedField: " + collectedField
				+ "; collectedAnnotations: " + collectedAnnotations + "]";
	}

	@Override
	public Class<?>[] getListenMemberClasses() {
		return LISTEN_CLASSES;
	}

}
